/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.pehulja.mmdo.coursework.math;


import java.io.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;

public class Matrix {
  private double[][] data;

  // create a square matrix initialized to the identity
  public Matrix(int dimension)
  {
    data = new double[dimension][dimension];
    for (int i = 0; i < dimension; i++)
      for (int j = 0; j < dimension; j++)
        data[i][j] = ((i==j) ? 1 : 0);
  		//{{INIT_CONTROLS
		//}}
}

  // create a matrix of any dimensions initialized
  // to all zeroes.
  public Matrix(int cols, int rows)
  {
    data = new double[rows][cols];
    for (int i = 0; i < rows; i++)
      for (int j = 0; j < cols; j++)
        data[i][j] = 0;
  }

  public Matrix(Matrix M) throws Exception
  {
    data = new double[M.rows()][M.cols()];
    copy(M);
  }

  // copy each cell from M to this, throwing an error
  // if M and this don't have the same dimensions.
  public void copy(Matrix M) throws Exception
  {
    if (M.rows() != rows() || M.cols() != cols())
      throw new Exception();

    subcopy(M);
  }

  // copy each cell from M to this (or a sub-matrix of
  // this with the dimensions of M), assuming M has
  // less then or equal number of rows and columns as this.
  public void subcopy(Matrix M) throws Exception
  {
    for (int i = 0; i < M.rows(); i++)
      for (int j = 0; j < M.cols(); j++)
        setCell(i, j, M.cell(i, j));
  }


  public int cols()
  {
    return data.length;
  }

  public int rows()
  {
    return (data.length > 0) ? data[0].length : 0;
  }

  // return the cell at the ith row and jth column
  public double cell(int i, int j)
  {
    return round(data[j][i]);
  }

  // set the value of the cell at the ith row and jth column
  public void setCell(int i, int j, double value)
  {
    data[j][i] = value;
  }

  /* Matrix addition */
  public Matrix add(Matrix M2) throws Exception
  {
    if (rows() != M2.rows() || cols() != M2.cols())
      throw new Exception();

    Matrix result = new Matrix(rows(), cols());
    for (int i = 0; i < cols(); i++)
      for (int j = 0; j < rows(); j++)
        result.setCell(i, j, round(cell(i, j) + M2.cell(i, j)));
    return result;
  }

  public static Matrix add(Matrix M1, Matrix M2) throws Exception
  { return M1.add(M2); }

  /* matrix subtraction */
  public Matrix subtract(Matrix M2) throws Exception
  {
    if (rows() != M2.rows() || cols() != M2.cols())
      throw new Exception();

    Matrix result = new Matrix(rows(), cols());
    for (int i = 0; i < cols(); i++)
      for (int j = 0; j < rows(); j++)
        result.setCell(i, j, cell(i, j) - M2.cell(i, j));
    return result;
  }

  public static Matrix subtract(Matrix M1, Matrix M2) throws Exception
  { return M1.subtract(M2); }

  /* scalar multiplication */
  public Matrix mult(double a) throws Exception
  {
    Matrix result = new Matrix(rows(), cols());
    result.copy(this);
    result.selfMult(a);
    return result;
  }

  public void selfMult(double a)
  {
    for (int i = 0; i < cols(); i++)
      for (int j = 0; j < rows(); j++)
        data[j][i] = round(round(data[i][j])*round(a));
  }

  /* vector multiplication */
  public static List mult(Matrix M1, List<Double> v2) throws Exception
  {
    if (M1.cols() != v2.size())
      throw new Exception();

    List <Double> result = new ArrayList<Double>(M1.rows());
    for (int j = 0; j < M1.rows(); j++) {
      double tmp = 0;
      for (int i = 0; i < M1.cols(); i++)
        tmp = tmp + round(M1.cell(i, j) * v2.get(i));
      result.set(j, tmp);
    }
    return result;
  }

  public static List mult(List<Double> v1, Matrix M2) throws Exception
  {
    if (v1.size() != M2.rows())
      throw new Exception();

    List result = new ArrayList(M2.cols());
    for (int i = 0; i < M2.cols(); i++) {
      double tmp = 0;
      for (int j = 0; j < M2.rows(); j++)
        tmp += round(M2.cell(i, j) * v1.get(j));
      result.set(i, tmp);
    }
    return result;
  }


  /* matrix multiplication */
  public Matrix mult(Matrix M2) throws Exception
  {
    if (cols() != M2.rows())
      throw new Exception();

    Matrix result = new Matrix(M2.cols(), rows());
    for (int j = 0; j < result.rows(); j++) {
      for (int i = 0; i < result.cols(); i++) {
        double tmp = 0;
        for (int k = 0; k < cols(); k++)
          tmp += round(cell(k, j) * M2.cell(i, k));
        result.setCell(i, j, tmp);
      }
    }
    return result;
  }

  public static Matrix mult(Matrix M1, Matrix M2) throws Exception
  { return M1.mult(M2); }

  // return the inverse of this matrix.
  public Matrix inverse() throws Exception
  {
    Matrix inv = new Matrix(rows());
    return inv.inverse(this);
  }

  // sets this Matrix to the inverse of the original Matrix
  // and returns this.
  public Matrix inverse(Matrix original) throws Exception
  {
    if (original.rows() < 1 || original.rows() != original.cols()
       || rows() != original.rows() || rows() != cols()) return this;
    int n = rows();
    copy(new Matrix(n)); // make identity matrix

    if (rows() == 1)
    {
      setCell(0, 0, 1 / original.cell(0, 0));
      return this;
    }

    Matrix b = new Matrix(original);

    for (int i = 0; i < n; i++)
    {
      // find pivot
      double mag = 0;
      int pivot = -1;

      for (int j = i; j < n; j ++)
      {
        double mag2 = Math.abs(b.cell(j, i));
        if (mag2 > mag)
        {
          mag = mag2;
          pivot = j;
        }
      }

      // no pivot (error)
      if (pivot == -1 || mag == 0)
      {
         return this;
      }

      // move pivot row into position
      if (pivot != i)
      {
        double temp;
        for (int j = i; j < n; j ++)
        {
          temp = b.cell(i, j);
          setCell(i, j, b.cell(pivot, j));
          b.setCell(pivot, j, temp);
        }

        for (int j = 0; j < n; j ++)
        {
          temp = cell(i, j);
          setCell(i, j, cell(pivot, j));
          setCell(pivot, j, temp);
        }
      }

      // normalize pivot row
      mag = b.cell(i, i);
      for (int j = i; j < n; j ++) b.setCell(i, j, b.cell(i, j) / mag);
      for (int j = 0; j < n; j ++) setCell(i, j, cell(i, j) / mag);

      // eliminate pivot row component from other rows
      for (int k = 0; k < n; k ++)
      {
        if (k == i) continue;
        double mag2 = b.cell(k, i);

        for (int j = i; j < n; j ++) b.setCell(k, j, b.cell(k, j) - round(mag2 * b.cell(i, j)));
        for (int j = 0; j < n; j ++) setCell(k, j, cell(k, j) - round(mag2 * cell(i, j)));
      }
    }
    return this;
  }


  public void print(PrintStream out)
  {
    String ln;
    for (int j = 0; j < rows(); j++) {
      ln = "";
      for (int i = 0; i < rows(); i++)
        ln += cell(i, j) + "\t";
      out.println(ln);
    }
  }
  
  public static double round(double i){
      return  new BigDecimal(i).setScale(2, RoundingMode.HALF_DOWN).doubleValue();
  }
	//{{DECLARE_CONTROLS
	//}}
}